package com.tony.demo.ui.performance.image;

//import com.bumptech.glide.disklrucache.DiskLruCache;  //原理一样，里面代码都几乎很少区别


import android.app.ActivityManager;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Build;
import android.os.Environment;
import android.util.LruCache;

import com.tony.demo.BuildConfig;
import com.tony.demo.ui.performance.image.disk.DiskLruCache;

import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

public class ImageCache {
    private static ImageCache instance = null;
    private WeakReference<Context> mContext;
    private LruCache<String, Bitmap> memoryCache;
    private Set<WeakReference<Bitmap>> reusablePool;
    private DiskLruCache mDiskLruCache;
    private ReferenceQueue<Bitmap> mReferenceQueue;
    private BitmapFactory.Options mOptions;

    private ImageCache(Context context) {
        mContext = new WeakReference<>(context);
        init();
    }

    public static ImageCache getInstance(Context context) {
        if (instance == null) {
            synchronized (ImageCache.class) {
                if (instance == null) {
                    instance = new ImageCache(context);
                }
            }
        }
        return instance;
    }

    private void init() {
        ActivityManager am = (ActivityManager) mContext.get().getSystemService(Context.ACTIVITY_SERVICE);
        if (null != am) {
            int memorySize = am.getMemoryClass();
            memoryCache = new MyLruCache(memorySize / 12 * 1024 * 1024);
        }
        mOptions = new BitmapFactory.Options();
        mOptions.inMutable = true;
        reusablePool = Collections.synchronizedSet(new HashSet<WeakReference<Bitmap>>());
        initDiskCache();
        initReferenceQueue();
    }

    private void initDiskCache() {
        String cachePath;
        File file = mContext.get().getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS);
        if (null != file) {
            cachePath = file.getAbsolutePath();
        } else {
            cachePath = mContext.get().getFilesDir().getAbsolutePath();
        }
        try {
            mDiskLruCache = DiskLruCache.open(new File(cachePath), BuildConfig.VERSION_CODE, 1, 20 * 1024 * 1204);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void initReferenceQueue() {
        if (null != mReferenceQueue) {
            mReferenceQueue = new ReferenceQueue<Bitmap>();
            //这里开个线程，无限循环去从引用对列中remove数据，是为了通过主动调用bitmap.recycle()方法来加快bitmap的回收。
            //因为Android GC是通过两次扫描来回收对象的，这里第一次扫描后标记会回收状态后会放入这个引用队列，在对二次扫描时才会真正回收。
            Thread clearQueue = new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true) {
                        try {
                            Bitmap bitmap = mReferenceQueue.remove().get();
                            if (null != bitmap && !bitmap.isRecycled()) {
                                bitmap.recycle();
                            }
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            });
            clearQueue.start();
        }
    }

    public void putToMemoryCache(String key, Bitmap bitmap) {
        memoryCache.put(key, bitmap);
    }

    public Bitmap getFromMemoryCache(String key) {
        return memoryCache.get(key);
    }

    public void putToDiskCache(String key, Bitmap bitmap) {
        DiskLruCache.Snapshot snapshot = null;
        try {
            snapshot = mDiskLruCache.get(key);
            if (null != snapshot) {
                return;
            }
            DiskLruCache.Editor edit = mDiskLruCache.edit(key);
            OutputStream outputStream = edit.newOutputStream(0);
            bitmap.compress(Bitmap.CompressFormat.JPEG, 50, outputStream);
            edit.commit();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (null != snapshot) {
                snapshot.close();
            }
        }
    }

    public Bitmap getFromDiskCache(String key, Bitmap reusable) {
        DiskLruCache.Snapshot snapshot = null;
        InputStream inputStream = null;
        try {
            snapshot = mDiskLruCache.get(key);
            if (null != snapshot) {
                inputStream = snapshot.getInputStream(0);
                mOptions.inMutable = true;
                mOptions.inBitmap = reusable;
                Bitmap bitmap = BitmapFactory.decodeStream(inputStream, null, mOptions);
                if (null != bitmap) {
                    putToMemoryCache(key, bitmap);
                    return bitmap;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (null != snapshot) {
                snapshot.close();
            }
            if (null != inputStream) {
                try {
                    inputStream.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }

    public Bitmap getReusableBitmap(int w, int h, int inSampleSize) {
        Iterator<WeakReference<Bitmap>> iterator = reusablePool.iterator();
        while (iterator.hasNext()) {
            Bitmap bitmap = iterator.next().get();
            if (null != bitmap && checkCanReuse(bitmap, w, h, inSampleSize)) {
                return bitmap;
            } else {
                iterator.remove();
            }
        }
        return null;
    }

    private boolean checkCanReuse(Bitmap bitmap, int w, int h, int inSampleSize) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
            return bitmap.getWidth() == w && bitmap.getHeight() == h && inSampleSize == 1;
        }
        return bitmap.getAllocationByteCount() >= w * h * checkPixel(bitmap.getConfig());
    }

    private int checkPixel(Bitmap.Config config) {
        if (config == Bitmap.Config.RGB_565) {
            return 2;
        } else if (config == Bitmap.Config.ARGB_8888) {
            return 4;
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && config == Bitmap.Config.RGBA_F16) {
            return 8;
        }
        return 2;
    }

    private class MyLruCache extends LruCache<String, Bitmap> {
        MyLruCache(int size) {
            super(size);
        }

        @Override
        protected int sizeOf(String key, Bitmap value) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                return value.getAllocationByteCount();
            }
            return value.getByteCount();
        }

        @Override
        protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
            if (oldValue.isMutable()) {
                reusablePool.add(new WeakReference<Bitmap>(oldValue, mReferenceQueue));
            } else {
                oldValue.recycle();
            }
        }
    }
}